package org.jspar.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jspar.Partition;
import org.jspar.tile.Position;
import org.jspar.tile.Rectangle;
import org.jspar.tile.Tile;

public class Module {
	
	public static final int INPUT  = 0;
	public static final int OUTPUT = 1;
	public static final int INOUT  = 2;
	public static final int MODULE = 3;
	
	// flags
	public static final int PLACED = 1;
	public static final int SEEN   = 2;

	// rotation
	public static final int R0   = 0;
	public static final int R90  = 1;
	public static final int R180 = 2;
	public static final int R270 = 3;

	private String name;
	private int flags;
	private int rotation;
	private Rectangle bounds = new Rectangle(0, 0, 0, 0);
	private List<Terminal> terminals = new ArrayList<Terminal>();
	private ModuleModel type;
	
	private Terminal primaryOut;
	private Terminal primaryIn;
	
	private Partition partition;
	private Tile htile;
	
	//private Expr fx;
	//private Expr fy;

	public Module(String name, ModuleModel type) {
		this.name = name;
		this.type = type;
		flags = 0;
		partition = null;
		primaryOut = null;
		primaryIn = null;
		rotation = R0;
		//fx = create_var_struct(ADD, null, null);
		//fy = create_var_struct(ADD, null, null);
		type.setDefaultObjectSize(this);
	}

	
	public String name() { return name; }
	public Terminal[] terminals() {
		return terminals.toArray(new Terminal[terminals.size()]);
	}
	
	public ModuleModel type() { return type; }

	public void setFlag(int i) { flags |= i; }
	public void clearFlag(int i) { flags &= ~i; }
	public boolean hasFlag(int i) { return (flags & i) != 0; }

	public void setPartition(Partition p) { partition = p; }
	public Partition partition() { return partition; }
	public void setPrimaryOut(Terminal t) { primaryOut = t; }
	public Terminal primaryOut() { return primaryOut; }
	public void setPrimaryIn(Terminal t) { primaryIn = t; }
	public Terminal primaryIn() { return primaryIn; }

	public Terminal outputTerminalConnecting(Module m) {
		if (m == null) return primaryOut;

		for (Terminal iterm : terminals) {
			if (iterm.isOut() || iterm.isInOut()) {
				for (Terminal jterm : m.terminals()) {
					if (iterm.net() == jterm.net())
						return iterm;
				}
			}
		}
		/* Default : (Probably will cause problems... */
		return primaryOut;
	}

	public int countTerminals(int s) {
		int count = 0;
		for (Terminal term : terminals) {
			if (term.side() == s)
				count++;
		}
		return count;
	}

	public int countTerminalsWith(Module m) {
		Set<Net> s = new HashSet<Net>();
		for (Terminal term : terminals) {
			if (term.isOut() || term.isInOut())
				s.add(term.net());
		}
		for (Terminal term : m.terminals()) {
			if (term.isIn() || term.isInOut())
				s.add(term.net());
		}
		return s.size();
	}

	/* Geometry handling
	 */

	public int x() { return bounds.x; }
	public int y() { return bounds.y; }
	public int width() { return bounds.width; }
	public int height() { return bounds.height; }
	
	public Position getPosition() {
		return bounds.getPosition();
	}
	
	public void setPosition(int x, int y) {
		bounds.setPosition(x, y);
	}

	public void rotate(int r) {
		rotation = r;
		Terminal[] terms = (Terminal[]) terminals();
		for (int  i = 0; i < terms.length; ++i) {
			terms[i].rotate(r);
		}
		if (r == R90 || r == R270) {
			bounds = new Rectangle(bounds.x, bounds.y, bounds.height, bounds.width);
		}
	}

	public void addTerminal(Terminal t) {
		terminals.add(t);
	}

	public void setHTile(Tile tile) {
		this.htile = tile;
	}

	public boolean isRoot() {
		if (type.isIn() || type.isInOut())
			return true;
		for (Terminal iterm : terminals()) {
			if (iterm.isIn() || iterm.isInOut()) {
				for (Terminal jterm : iterm.net().terminals()) {
					if (partition() != jterm.module().partition())
						return true;
				}
			}
		}
		return false;
	}

	public Terminal getTerminal(String termName) {
		for (Terminal t : terminals) {
			if (t.name().equals(termName)) {
				return t;
			}
		}
		return null;
	}

	public void setSize(int width, int height) {
		this.bounds.setSize(width, height);
	}
	
	public Rectangle getBounds() {
		return bounds;
	}

	@Override
	public String toString() {
		return name;
	}
	
}
